﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;

namespace Beaker.OpenCube.Render.Maths
{
	[DebuggerDisplay( "[{X},{Y},{Z}]" )]
	public struct Vector3
		: IEquatable<Vector3>, IEnumerable<double>
	{
		public static readonly Vector3 Zero = new Vector3( 0d, 0d, 0d );
		public static readonly Vector3 One = new Vector3( 1d, 1d, 1d );
		public static readonly Vector3 NaN = new Vector3( double.NaN, double.NaN, double.NaN );

		private readonly double _x;
		private readonly double _y;
		private readonly double _z;

		public Vector3( double x, double y, double z )
		{
			_x = x;
			_y = y;
			_z = z;
		}

		#region Value

		/// <summary>
		/// Gets the X offset of the vector
		/// </summary>
		[Pure]
		public double X
		{
			
			get { return _x; }
		}

		/// <summary>
		/// Gets the Y offset of the vector
		/// </summary>
		[Pure]
		public double Y
		{
			
			get { return _y; }
		}

		/// <summary>
		/// Gets the Z offset of the vector
		/// </summary>
		[Pure]
		public double Z
		{
			
			get { return _z; }
		}

		[Pure]
		public double this[int index]
		{
			get
			{
				switch( index )
				{
					case 0:
						return _x;
					case 1:
						return _y;
					case 2:
						return _z;
				}

				throw new ArgumentOutOfRangeException( "index" );
			}
		}

		[Pure]
		public IEnumerator<double> GetEnumerator()
		{
			yield return _x;
			yield return _y;
			yield return _z;
		}

		[Pure]
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion
		#region Negate

		[Pure]
		
		public Vector3 Negate()
		{
			return
				new Vector3(
					-_x,
					-_y,
					-_z
				);
		}

		[Pure]
		
		public static Vector3 Negate( Vector3 v )
		{
			return v.Negate();
		}

		[Pure]
		
		public static Vector3 operator -( Vector3 v )
		{
			return v.Negate();
		}

		#endregion
		#region Addition

		/// <summary>
		/// Adds the second vector to this second to create a new vector
		/// </summary>
		/// <param name="other">The other vector to add to this vector</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public Vector3 Add( Vector3 other )
		{
			return new Vector3( _x + other._x, _y + other._y, _z + other._z );
		}

		/// <summary>
		/// Add the second vector to the first vector to create a new vector
		/// </summary>
		/// <param name="left">The first vector.</param>
		/// <param name="right">The second vector, to add to the first vector.</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public static Vector3 Add( Vector3 left, Vector3 right )
		{
			return left.Add( right );
		}

		/// <summary>
		/// Add the second vector to the first vector to create a new vector
		/// </summary>
		/// <param name="left">The first vector.</param>
		/// <param name="right">The second vector, to add to the first vector.</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public static Vector3 operator +( Vector3 left, Vector3 right )
		{
			return left.Add( right );
		}

		#endregion
		#region Subraction

		/// <summary>
		/// Subtracts the second vector to this second to create a new vector
		/// </summary>
		/// <param name="other">The other vector to subtract from this vector</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public Vector3 Subtract( Vector3 other )
		{
			return new Vector3( _x - other._x, _y - other._y, _z - other._z );
		}

		/// <summary>
		/// Subtracts the second vector to this second to create a new vector
		/// </summary>
		/// <param name="other">The other vector to subtract from this vector</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public static Vector3 Subtract( Vector3 left, Vector3 right )
		{
			return left.Subtract( right );
		}

		/// <summary>
		/// Subtracts the second vector to the first vector to create a new vector
		/// </summary>
		/// <param name="left">The first vector.</param>
		/// <param name="right">The second vector, to subtract from the first vector.</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public static Vector3 operator -( Vector3 left, Vector3 right )
		{
			return left.Subtract( right );
		}

		#endregion
		#region Multiplication (normal, dot and cross)

		/// <summary>
		/// Multiplies the vector by the given number
		/// </summary>
		/// <param name="other">The number to multiply the vector with.</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public Vector3 Multiply( double other )
		{
			return new Vector3( _x * other, _y * other, _z * other );
		}

		/// <summary>
		/// Multiplies the vector by the given number
		/// </summary>
		/// <param name="left">The vector to multiply.</param>
		/// <param name="right">The number to multiply the vector with.</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public static Vector3 Multiply( Vector3 left, double right )
		{
			return left.Multiply( right );
		}

		/// <summary>
		/// Multiplies the vector by the given number
		/// </summary>
		/// <param name="left">The vector to multiply.</param>
		/// <param name="right">The number to multiply the vector with.</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public static Vector3 Multiply( double left, Vector3 right )
		{
			// order of multiplication does not matter
			return right.Multiply( left );
		}

		/// <summary>
		/// Multiplies the vector by the given number
		/// </summary>
		/// <param name="left">The vector to multiply.</param>
		/// <param name="right">The number to multiply the vector with.</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public static Vector3 operator *( Vector3 left, double right )
		{
			return left.Multiply( right );
		}

		/// <summary>
		/// Multiplies the vector by the given number
		/// </summary>
		/// <param name="left">The vector to multiply.</param>
		/// <param name="right">The number to multiply the vector with.</param>
		/// <returns>The new vector that is the result of the operation</returns>
		[Pure]
		
		public static Vector3 operator *( double left, Vector3 right )
		{
			// order of multiplication does not matter
			return right.Multiply( left );
		}

		/// <summary>
		/// Computes the Dot Product of two vectors
		/// </summary>
		/// <param name="other">The other vector.</param>
		/// <returns>The dot product of the two vectors</returns>
		[Pure]
		
		public double DotProduct( Vector3 other )
		{
			return _x * other._x + _y * other._y + _z * other._z;
		}

		/// <summary>
		/// Computes the Dot Product of two vectors
		/// </summary>
		/// <param name="left">The first vector.</param>
		/// <param name="right">The second vector.</param>
		/// <returns>The dot product of the two vectors</returns>
		[Pure]
		
		public static double DotProduct( Vector3 left, Vector3 right )
		{
			return left.DotProduct( right );
		}

		/// <summary>
		/// Computes the Dot Product of two vectors
		/// </summary>
		/// <param name="left">The first vector.</param>
		/// <param name="right">The second vector.</param>
		/// <returns>The dot product of the two vectors</returns>
		[Pure]
		
		public static double operator *( Vector3 left, Vector3 right )
		{
			return left.DotProduct( right );
		}

		[Pure]
		
		public Vector3 CrossProduct( Vector3 other )
		{
			return new Vector3(
				_y * other._z - _z * other._y,
				_z * other._x - _x * other._z,
				_x * other._y - _y * other._x
			);
		}

		/// <summary>
		/// Computes the Cross Product of two vectors
		/// </summary>
		/// <param name="left">The first vector.</param>
		/// <param name="right">The second vector.</param>
		/// <returns>The cross product of the two vectors</returns>
		[Pure]
		
		public static Vector3 CrossProduct( Vector3 left, Vector3 right )
		{
			return left.CrossProduct( right );
		}

		/// <summary>
		/// Computes the Cross Product of two vectors
		/// </summary>
		/// <param name="left">The first vector.</param>
		/// <param name="right">The second vector.</param>
		/// <returns>The cross product of the two vectors</returns>
		[Pure]
		
		public static Vector3 operator ^( Vector3 left, Vector3 right )
		{
			return left.CrossProduct( right );
		}

		#endregion
		#region Division

		[Pure]
		
		public Vector3 Divide( double d )
		{
			double invD = 1d / d;

			return
				new Vector3(
					_x * invD,
					_y * invD,
					_z * invD
				);
		}

		[Pure]
		
		public static Vector3 Divide( Vector3 left, double right )
		{
			return left.Divide( right );
		}

		[Pure]
		
		public static Vector3 operator /( Vector3 left, double right )
		{
			return left.Divide( right );
		}

		#endregion
		#region Magnitude

		[Pure]
		
		public double Magnitude()
		{
			return Math.Sqrt( _x * _x + _y * _y + _z * _z );
		}

		[Pure]
		
		public static double Magnitude( Vector3 v )
		{
			return v.Magnitude();
		}

		[Pure]
		
		public double MagnitudePower()
		{
			return _x * _x + _y * _y + _z * _z;
		}

		[Pure]
		
		public static double MagnitudePower( Vector3 v )
		{
			return v.MagnitudePower();
		}

		#endregion
		#region Normal

		/// <summary>
		/// Computes the normal of the vector
		/// </summary>
		/// <returns>
		/// The normal of the vector
		/// </returns>
		[Pure]
		
		public Vector3 Normalize()
		{
			double mag = Magnitude();

			if( mag == 0d )
				return Vector3.NaN;

			return Divide( mag );
		}

		/// <summary>
		/// Computes the normal of the vector.
		/// </summary>
		/// <param name="v">The vector to compute the normal for.</param>
		/// <returns>
		/// The normal of the vector.
		/// </returns>
		[Pure]
		
		public static Vector3 Normalize( Vector3 v )
		{
			return v.Normalize();
		}

		/// <summary>
		/// Determines whether this instance is normalized.
		/// </summary>
		/// <returns>
		///   <c>true</c> if this instance is normalized; otherwise, <c>false</c>.
		/// </returns>
		[Pure]
		
		public bool IsNormalized()
		{
			return Magnitude().AboutEqual( 1d );
		}

		#endregion

		#region Equality

		[Pure]
		public override bool Equals( object obj )
		{
			if( ReferenceEquals( null, obj ) ) return false;
			if( ReferenceEquals( this, obj ) ) return true;
			if( obj.GetType() != this.GetType() ) return false;
			return Equals( (Vector3)obj );
		}

		[Pure]
		public bool Equals( Vector3 other )
		{
			if( ReferenceEquals( null, other ) ) return false;
			if( ReferenceEquals( this, other ) ) return true;
			return _x.Equals( other._x ) && _y.Equals( other._y ) && _z.Equals( other._z );
		}

		[Pure]
		
		public static bool operator ==( Vector3 left, Vector3 right )
		{
			return Equals( left, right );
		}

		[Pure]
		
		public static bool operator !=( Vector3 left, Vector3 right )
		{
			return !Equals( left, right );
		}

		[Pure]
		public override int GetHashCode()
		{
			unchecked
			{
				int hashCode = _x.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _y.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _z.GetHashCode();
				return hashCode;
			}
		}

		#endregion
	}

	public static class Vector3Extensions
	{
		[Pure]
		
		public static Vector3 Multiply( this double d, Vector3 v )
		{
			return v.Multiply( d );
		}
	}
}
